/**
 * 
 */
package jmine.tec.utils.reflection.fast;

/**
 * @author takeshi
 */
enum MatchLevel {

    PERFECT {
            /**
         * {@inheritDoc}
         */
        @Override
        public boolean match(Class<?> arg0, Class<?> arg1) {
            return arg0 == arg1;
        }
        },
    SUPER_TYPE {
            /**
         * {@inheritDoc}
         */
        @Override
        public boolean match(Class<?> arg0, Class<?> arg1) {
            if (arg1 == null) {
                return !arg0.isPrimitive();
            }
            return arg0.isAssignableFrom(arg1);
        }
        },
    PRIMITIVE_TYPE_WRAPPER {
            /**
         * {@inheritDoc}
         */
        @Override
        public boolean match(Class<?> arg0, Class<?> arg1) {
            return SUPER_TYPE.match(arg0, arg1) || AbstractFastClass.promotePrimitive(arg0) == AbstractFastClass.promotePrimitive(arg1);
        }

    },
    PRIMITIVE_WIDENING {
            /**
         * {@inheritDoc}
         */
        @Override
        public boolean match(Class<?> arg0, Class<?> arg1) {
            return PRIMITIVE_TYPE_WRAPPER.match(arg0, arg1) || this.isExpandable(arg0, arg1);
        }

        /**
         * @param arg0 {@link Class}
         * @param arg1 {@link Class}
         * @return boolean
         */
        private boolean isExpandable(Class<?> arg0, Class<?> arg1) {
            if (!arg0.isPrimitive()) {
                return false;
            }
            Class<?> a0 = AbstractFastClass.promotePrimitive(arg0);
            Class<?> a1 = AbstractFastClass.promotePrimitive(arg1);
            int i0 = this.indexFor(a0);
            if (i0 < 0) {
                return false;
            }
            int i1 = this.indexFor(a1);
            if (i1 < 0) {
                return false;
            }
            return i0 >= i1;
        }

        /**
         * @param type {@link Class}
         * @return int
         */
        private int indexFor(Class<?> type) {
            for (int i = 0; i < this.widening.length; i++) {
                if (type == this.widening[i]) {
                    return i;
                }
            }
            return -1;
        }

        private final Class<?>[] widening = { Byte.class, Short.class, Integer.class, Float.class, Long.class, Double.class };

    };

    /**
     * @param declaredType {@link Class}
     * @param foundType {@link Class}
     * @return boolean
     */
    public abstract boolean match(Class<?> declaredType, Class<?> foundType);

    /**
     * @param declared array de classes
     * @param found array de classes
     * @return boolean
     */
    public boolean matchesAll(Class<?>[] declared, Class<?>[] found) {
        for (int i = 0; i < found.length; i++) {
            Class<?> a = declared[i];
            Class<?> b = found[i];
            if (!this.match(a, b)) {
                return false;
            }
        }
        return true;
    }

}